/**    
* @Title: RomoteConfScheduler.java  
* @Package com.kaver.spring   
* @author roc   
* @date 2019年1月31日 上午9:44:05  
* @version V1.0  
* @copyright kaver 
*/
package com.kaver.spring;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import com.alibaba.fastjson.JSON;
import com.kaver.annotation.AutoUpdate;
import com.kaver.bean.ConfigItem;
import com.kaver.bean.PullDto;
import com.kaver.exception.ConfException;
import com.kaver.listener.ChangeType;
import com.kaver.utils.AopTargetUtils;
import com.kaver.utils.FieldReflectionUtil;
import com.kaver.utils.HttpUtil;

/**
 * @ClassName: RomoteConfScheduler
 * @Description: 远程配置拉取定时
 * @author roc
 * @date 2019年1月31日 上午9:44:05
 * 
 */
public class RomoteConfScheduler extends InstantiationAwareBeanPostProcessorAdapter implements ApplicationContextAware {
	private final static Logger logger = LoggerFactory.getLogger(RomoteConfScheduler.class);
	private ScheduledExecutorService hbScheduledExecutor = Executors.newScheduledThreadPool(1);
	private Map<String, List<BeanField>> valueBeans = new ConcurrentHashMap<>();
	private static ApplicationContext applicationContext;
	private static final String PLACEHOLDERPREFIX = "${";
	private static final String PLACEHOLDERSUFFIX = "}";
	private static final String PLACEHOLDERDEFAULTVALUE = ":";

	public void schedulePullProperty() {
		hbScheduledExecutor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				merge(dohttp());
			}
		}, 30, 30, TimeUnit.SECONDS);
	}

	private PullDto dohttp() {
		Map<String, String> params = new HashMap<>();
		params.put("appName", AppInstance.getAppName());
		params.put("env", AppInstance.getEnv());
		params.put("version", String.valueOf(AppInstance.getVersion()));
		try {
			String json = HttpUtil.doPost(params, new HashMap<>(), AppInstance.getServerUrl() + "/pull");
			PullDto dto = JSON.parseObject(json, PullDto.class);
			RomoteRepository.setEnable(true);
			return dto;
		} catch (IOException e) {
			RomoteRepository.setEnable(false);
			logger.error("执行拉取配置失败", e);
			e.printStackTrace();
		}
		return null;
	}

	private void merge(PullDto pullDto) {
		if (pullDto != null) {
			LocalCacheRepository localCacheRepository = LocalCacheRepository.getInstance();
			List<ConfigItem> add = pullDto.getAddItems();
			if (!CollectionUtils.isEmpty(add)) {
			    logger.info("新增配置:" + JSON.toJSONString(add));
			}
			refresh(add, ChangeType.ADD);
			localCacheRepository.mergeMe(itemToProperties(add), ChangeType.ADD);
			List<ConfigItem> del = pullDto.getDelItems();
			if (!CollectionUtils.isEmpty(del)) {
                logger.info("删除配置:" + JSON.toJSONString(del));
            }
			refresh(del, ChangeType.DELETE);
			localCacheRepository.mergeMe(itemToProperties(del), ChangeType.DELETE);
			List<ConfigItem> update = pullDto.getUpdateItems();
			if (!CollectionUtils.isEmpty(update)) {
                logger.info("更新配置:" + JSON.toJSONString(update));
            }
			refresh(update, ChangeType.UPDATE);
			localCacheRepository.mergeMe(itemToProperties(update), ChangeType.UPDATE);
			if (pullDto.getVersion() != null) {
				AppInstance.setVersion(pullDto.getVersion());
			}
			localCacheRepository.restore();
		}
	}

	private Properties itemToProperties(List<ConfigItem> items) {
		Properties properties = new Properties();
		if (items != null && !items.isEmpty()) {
			for (ConfigItem item : items) {
				properties.put(item.getKey(), item.getValue());
			}
		}
		return properties;
	}

	private void refresh(List<ConfigItem> configItems, ChangeType type) {
		if (configItems != null && !configItems.isEmpty()) {
			for (ConfigItem item : configItems) {
				refreshBeanValue(item, type);
				try {
					ListenerContext.onChange(item.getKey(), item.getValue(), type);
				} catch (Exception e) {
					e.printStackTrace();
					logger.error("onChange error", e);
				}
			}
		}
	}

	public void refreshBeanValue(ConfigItem item, ChangeType type) {
		if (valueBeans.containsKey(item.getKey())) {
		    logger.info("刷新beanvalue:" + JSON.toJSONString(item));
			List<BeanField> beanFields = valueBeans.get(item.getKey());
			for (BeanField beanField : beanFields) {
				Object bean = AopTargetUtils.getTarget(applicationContext.getBean(beanField.getBeanName()));
				BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
				PropertyDescriptor propertyDescriptor = null;
				PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
				if (propertyDescriptors != null && propertyDescriptors.length > 0) {
					for (PropertyDescriptor descriptor : propertyDescriptors) {
						if (beanField.getProperty().equals(descriptor.getName())) {
							propertyDescriptor = descriptor;
						}
					}
				}
				if (propertyDescriptor != null && propertyDescriptor.getWriteMethod() != null) {
					beanWrapper.setPropertyValue(beanField.getProperty(), item.getValue());
				} else {
					ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
						@Override
						public void doWith(Field fieldItem) throws IllegalArgumentException, IllegalAccessException {
							if (beanField.getProperty().equals(fieldItem.getName())) {
								try {
									Object valueObj = null;
									if (!ChangeType.DELETE.equals(type)) {
										valueObj = FieldReflectionUtil.parseValue(fieldItem.getType(), item.getValue());
									}
									fieldItem.setAccessible(true);
									fieldItem.set(bean, valueObj);
								} catch (IllegalAccessException e) {
									throw new ConfException(e);
								}
							}
						}
					});
				}
			}
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		RomoteConfScheduler.applicationContext = applicationContext;
		schedulePullProperty();
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Field[] fields = AopUtils.getTargetClass(bean).getDeclaredFields();
		if (fields != null && fields.length > 0) {
			for (Field field : fields) {
				if (field.isAnnotationPresent(AutoUpdate.class) && field.isAnnotationPresent(Value.class)) {
					Value value = field.getAnnotation(Value.class);
					String key = keyResolver(value.value());
					BeanField beanField = new BeanField();
					beanField.setBeanName(beanName);
					beanField.setProperty(field.getName());
					if (valueBeans.containsKey(key)) {
						List<BeanField> list = valueBeans.get(key);
						list.add(beanField);
					} else {
						List<BeanField> list = new ArrayList<RomoteConfScheduler.BeanField>();
						list.add(beanField);
						valueBeans.put(key, list);
					}
				}
			}
		}
		return bean;
	}

	public static class BeanField {
		private String beanName;
		private String property;

		public BeanField() {
		}

		public BeanField(String beanName, String property) {
			this.beanName = beanName;
			this.property = property;
		}

		public String getBeanName() {
			return beanName;
		}

		public void setBeanName(String beanName) {
			this.beanName = beanName;
		}

		public String getProperty() {
			return property;
		}

		public void setProperty(String property) {
			this.property = property;
		}
	}

	private static String keyResolver(String placeHolder) {
		if (placeHolder == null) {
			return placeHolder;
		}
		if (placeHolder.startsWith(PLACEHOLDERPREFIX) && placeHolder.endsWith(PLACEHOLDERSUFFIX)) {
			String key = placeHolder.substring(placeHolder.indexOf(PLACEHOLDERPREFIX) + 2,
					placeHolder.indexOf(PLACEHOLDERSUFFIX));
			if (key.contains(PLACEHOLDERDEFAULTVALUE)) {
				key = key.substring(0, key.indexOf(PLACEHOLDERDEFAULTVALUE));
			}
			return key;
		}
		return placeHolder;
	}
}
